/*           Copyright(c) 2005-2008 Dave Sexton            *
 *                   HtmlEditor Control                    *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using mshtml;
using DaveSexton.Controls.Native;
using DaveSexton.Controls.Resources;
using System.Collections.ObjectModel;

namespace DaveSexton.Controls
{
	public partial class HtmlCommand
	{
		#region Public Properties
		public virtual bool Indeterminate
		{
			get
			{
				return (document == null)
					? (textRange == null)
						? controlRange.queryCommandIndeterm(commandId)
						: textRange.queryCommandIndeterm(commandId)
					: document.queryCommandIndeterm(commandId);
			}
		}

		public virtual bool Enabled
		{
			get
			{
				return (document == null)
					? (textRange == null)
						? controlRange.queryCommandEnabled(commandId)
						: textRange.queryCommandEnabled(commandId)
					: document.queryCommandEnabled(commandId);
			}
		}

		public virtual bool Activated
		{
			get
			{
				return (document == null)
					? (textRange == null)
						? controlRange.queryCommandState(commandId)
						: textRange.queryCommandState(commandId)
					: document.queryCommandState(commandId);
			}
		}

		public virtual bool Supported
		{
			get
			{
				return (document == null)
					? (textRange == null)
						? controlRange.queryCommandSupported(commandId)
						: textRange.queryCommandSupported(commandId)
					: document.queryCommandSupported(commandId);
			}
		}

		public virtual string Text
		{
			get
			{
				return (document == null)
					? (textRange == null)
						? controlRange.queryCommandText(commandId)
						: textRange.queryCommandText(commandId)
					: document.queryCommandText(commandId);
			}
		}

		public object Source
		{
			get
			{
				return (document == null)
					? (textRange == null)
						? (object) controlRange
						: (object) textRange
					: (object) document;
			}
		}

		public HtmlCommandIdentifier Command
		{
			get
			{
				return command;
			}
		}

		public HtmlSelection Selection
		{
			get
			{
				if (selection == null)
				{
					if (textRange != null)
						selection = new HtmlSelection(editor, textRange);
					else if (controlRange != null)
						selection = new HtmlSelection(editor, controlRange);
					else if (document != null)
						selection = new HtmlSelection(editor, false);
				}

				return selection;
			}
		}

		public string CommandId
		{
			get
			{
				return commandId;
			}
		}

		public HtmlEditor Editor
		{
			get
			{
				return editor;
			}
		}

		public bool NotifyHtmlChangedOnExecute
		{
			get
			{
				return notifyHtmlChangedOnExecute;
			}
			set
			{
				notifyHtmlChangedOnExecute = value;
			}
		}
		#endregion

		#region Private / Protected
		private static readonly Guid CGID_MSHTML = new Guid("DE4BA900-59CA-11CF-9592-444553540000");

		internal ReadOnlyCollection<IHTMLElement> SelectedElementsInternal
		{
			get
			{
				return new ReadOnlyCollection<IHTMLElement>(new List<IHTMLElement>(Selection.SelectedElementsInternal));
			}
		}

		private HtmlSelection selection;
		private readonly HtmlEditor editor;
		private readonly HtmlCommandIdentifier command;
		private readonly string commandId;
		private readonly IHTMLDocument2 document;
		private readonly IHTMLTxtRange textRange;
		private readonly IHTMLControlRange controlRange;
		private bool notifyHtmlChangedOnExecute;
		#endregion

		#region Constructors
		public HtmlCommand(HtmlCommandIdentifier command, string commandId, HtmlEditor editor, HtmlSelection selection)
			: this(command, commandId, editor, selection, null)
		{
		}

		public HtmlCommand(HtmlCommandIdentifier command, string commandId, HtmlEditor editor, object target)
			: this(command, commandId, editor, null, target)
		{
		}

		public HtmlCommand(HtmlCommandIdentifier command, string commandId, HtmlEditor editor)
			: this(command, commandId, editor, null, null)
		{
		}

		public HtmlCommand(HtmlCommandIdentifier command, HtmlEditor editor, HtmlSelection selection)
			: this(command, editor, selection, null)
		{
		}

		public HtmlCommand(HtmlCommandIdentifier command, HtmlEditor editor, object target)
			: this(command, editor, null, target)
		{
		}

		public HtmlCommand(HtmlCommandIdentifier command, HtmlEditor editor)
			: this(command, editor, null, null)
		{
		}

		public HtmlCommand(HtmlCommandIdentifier command, HtmlEditor editor, HtmlSelection selection, object target)
			: this(command, null, editor, selection, target)
		{
		}

		public HtmlCommand(HtmlCommandIdentifier command, string commandId, HtmlEditor editor, HtmlSelection selection, object target)
		{
			if (!Enum.IsDefined(typeof(HtmlCommandIdentifier), command))
				throw new System.ComponentModel.InvalidEnumArgumentException("command", (int) command, typeof(HtmlCommandIdentifier));

			if (editor == null)
				throw new ArgumentNullException("editor");

			if (target == null)
				target = document = editor.domDocument;
			else
			{
				document = target as IHTMLDocument2;

				if (document == null)
				{
					textRange = target as IHTMLTxtRange;

					if (textRange == null)
					{
						controlRange = target as IHTMLControlRange;

						if (controlRange == null)
							throw new ArgumentException(Errors.InvalidCommandTarget, "target");
						else if (editor.domDocument != controlRange.commonParentElement().document)
							throw new ArgumentException(Errors.EditorDocumentIsDifferentThanArgument, "target");
					}
					else if (editor.domDocument != textRange.parentElement().document)
						throw new ArgumentException(Errors.EditorDocumentIsDifferentThanArgument, "target");
				}
				else if (editor.domDocument != document)
					throw new ArgumentException(Errors.EditorDocumentIsDifferentThanArgument, "target");
			}

			this.selection = selection; // allow null
			this.editor = editor;
			this.command = command;
			this.commandId = (string.IsNullOrEmpty(commandId)) ? GetCommandID(command) : commandId;
			
			notifyHtmlChangedOnExecute = ShouldNotifyHtmlChangedOnExecute(command);
		}
		#endregion

		#region Methods
		public static bool Execute(HtmlCommandIdentifier command, HtmlEditor editor, bool showUI, object value)
		{
			if (!Enum.IsDefined(typeof(HtmlCommandIdentifier), command))
				throw new System.ComponentModel.InvalidEnumArgumentException("command", (int) command, typeof(HtmlCommandIdentifier));

			if (editor == null)
				throw new ArgumentNullException("editor");

			bool success = editor.domDocument.execCommand(GetCommandID(command), showUI, value);

			if (success && ShouldNotifyHtmlChangedOnExecute(command))
				editor.NotifyHtmlChanged(HtmlChangedReason.CommandNotification);

			return success;
		}

		[CLSCompliant(false)]
		public static T Execute<T>(uint command, HtmlEditor editor, object value, T resultIfUnsuccessful)
		{
			return Execute<T>(command, editor, value, resultIfUnsuccessful, null);
		}

		[CLSCompliant(false)]
		public static T Execute<T>(uint command, HtmlEditor editor, object value, T resultIfUnsuccessful, bool? showUI)
		{
			if (editor == null)
				throw new ArgumentNullException("editor");

			Native.IOleCommandTarget target = (Native.IOleCommandTarget) editor.domDocument;

			Guid guid = CGID_MSHTML;
			object oResult = null;

			bool success = target.Exec(ref guid, command,
				(showUI.HasValue) ? (showUI.Value) ? Native.OleCmdExecOpt.PROMPTUSER : Native.OleCmdExecOpt.DONTPROMPTUSER : Native.OleCmdExecOpt.DODEFAULT,
				new object[] { value }, ref oResult);

			T result = (T) (oResult ?? default(T));
			
			if (success)
			{
				if (ShouldNotifyHtmlChangedOnExecute(command))
					editor.NotifyHtmlChanged(HtmlChangedReason.CommandNotification);

				return result;
			}
			else
				return resultIfUnsuccessful;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "5#"), CLSCompliant(false)]
		public static bool QueryStatus(uint command, bool throwUnsupported, object target, OleCommandQueryTypes query, OleCommandTextType textQuery, out string text)
		{
			if (target == null)
				throw new ArgumentNullException("target");

			IOleCommandTarget oleTarget = target as IOleCommandTarget;

			if (oleTarget == null)
				throw new ArgumentException(Errors.InvalidCommandTarget, "target");

			if (!Enum.IsDefined(typeof(OleCommandQueryTypes), query))
				throw new System.ComponentModel.InvalidEnumArgumentException("query", (int) query, typeof(OleCommandQueryTypes));

			if (!Enum.IsDefined(typeof(OleCommandTextType), textQuery))
				throw new System.ComponentModel.InvalidEnumArgumentException("textQuery", (int) textQuery, typeof(OleCommandTextType));

			using (OleCommand cmd = new OleCommand(command))
			{
				using (OleCommandText info = (textQuery == OleCommandTextType.None)
					? new OleCommandText()
					: new OleCommandText(1024, textQuery))	 // required, but not used here
				{
					Guid guid = CGID_MSHTML;
					oleTarget.QueryStatus(ref guid, 1, cmd.Handle, info.Handle);

					text = info.Text;

					if ((cmd.QueryTypes & OleCommandQueryTypes.Supported) == OleCommandQueryTypes.Supported)
						return (cmd.QueryTypes & query) == query;
					else if (throwUnsupported)
						throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
							Errors.UnsupportedCommand, command));
					else
						return false;
				}
			}
		}

		[CLSCompliant(false)]
		public static bool IsEnabled(uint command, bool throwUnsupported, object target)
		{
			string text;
			return QueryStatus(command, throwUnsupported, target, OleCommandQueryTypes.Enabled, OleCommandTextType.None, out text);
		}

		[CLSCompliant(false)]
		public static bool IsActivated(uint command, bool throwUnsupported, object target)
		{
			string text;
			return QueryStatus(command, throwUnsupported, target, OleCommandQueryTypes.Latched, OleCommandTextType.None, out text);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
		private static bool ShouldNotifyHtmlChangedOnExecute(uint command)
		{
			switch (command)
			{
				case HtmlCommand.MSHtml.ApplyHeadings1:
				case HtmlCommand.MSHtml.ApplyHeadings2:
				case HtmlCommand.MSHtml.ApplyHeadings3:
				case HtmlCommand.MSHtml.ApplyNormal:
				case HtmlCommand.MSHtml.BackColor:
				case HtmlCommand.MSHtml.Blink:
				case HtmlCommand.MSHtml.BlockDirectionLeftToRight:
				case HtmlCommand.MSHtml.BlockDirectionRightToLeft:
				case HtmlCommand.MSHtml.BlockFormat:
				case HtmlCommand.MSHtml.Bold:
				case HtmlCommand.MSHtml.Bookmark:
				case HtmlCommand.MSHtml.BorderColor:
				case HtmlCommand.MSHtml.Button:
				case HtmlCommand.MSHtml.CaptionInsert:
				case HtmlCommand.MSHtml.CellInsert:
				case HtmlCommand.MSHtml.CellSplit:
				case HtmlCommand.MSHtml.CenterAlignParagraph:
				case HtmlCommand.MSHtml.ChangeCase:
				case HtmlCommand.MSHtml.ChangeFont:
				case HtmlCommand.MSHtml.ChangeFontSize:
				case HtmlCommand.MSHtml.CheckBox:
				case HtmlCommand.MSHtml.Chiseled:
				case HtmlCommand.MSHtml.ColumnInsert:
				case HtmlCommand.MSHtml.Comment:
				case HtmlCommand.MSHtml.CreateLink:
				case HtmlCommand.MSHtml.DeleteWord:
				case HtmlCommand.MSHtml.DirectionLeftToRight:
				case HtmlCommand.MSHtml.DirectionRightToLeft:
				case HtmlCommand.MSHtml.Div:
				case HtmlCommand.MSHtml.DropDownBox:
				case HtmlCommand.MSHtml.Element1D:
				case HtmlCommand.MSHtml.Element2D:
				case HtmlCommand.MSHtml.Etched:
				case HtmlCommand.MSHtml.Flat:
				case HtmlCommand.MSHtml.Font:
				case HtmlCommand.MSHtml.FontName:
				case HtmlCommand.MSHtml.FontSize:
				case HtmlCommand.MSHtml.ForeColor:
				case HtmlCommand.MSHtml.Form:
				case HtmlCommand.MSHtml.FormatMark:
				case HtmlCommand.MSHtml.HorizontalLine:
				case HtmlCommand.MSHtml.HtmlArea:
				case HtmlCommand.MSHtml.Hyperlink:
				case HtmlCommand.MSHtml.IE5Paste:
				case HtmlCommand.MSHtml.IE5PasteNode:
				case HtmlCommand.MSHtml.IFrame:
				case HtmlCommand.MSHtml.Image:
				case HtmlCommand.MSHtml.ImageMap:
				case HtmlCommand.MSHtml.Import:
				case HtmlCommand.MSHtml.Indent:
				case HtmlCommand.MSHtml.InlineDirectionLeftToRight:
				case HtmlCommand.MSHtml.InlineDirectionRightToLeft:
				case HtmlCommand.MSHtml.InsertFieldset:
				case HtmlCommand.MSHtml.InsertInputButton:
				case HtmlCommand.MSHtml.InsertInputHidden:
				case HtmlCommand.MSHtml.InsertInputImage:
				case HtmlCommand.MSHtml.InsertInputPassword:
				case HtmlCommand.MSHtml.InsertInputReset:
				case HtmlCommand.MSHtml.InsertInputSubmit:
				case HtmlCommand.MSHtml.InsertInputUpload:
				case HtmlCommand.MSHtml.InsertObject:
				case HtmlCommand.MSHtml.InsertSpan:
				case HtmlCommand.MSHtml.Italic:
				case HtmlCommand.MSHtml.JavaApplet:
				case HtmlCommand.MSHtml.JustifyCenter:
				case HtmlCommand.MSHtml.JustifyFull:
				case HtmlCommand.MSHtml.JustifyGeneral:
				case HtmlCommand.MSHtml.JustifyLeft:
				case HtmlCommand.MSHtml.JustifyNone:
				case HtmlCommand.MSHtml.JustifyRight:
				case HtmlCommand.MSHtml.Language:
				case HtmlCommand.MSHtml.LeftAlignParagraph:
				case HtmlCommand.MSHtml.LineBreakBoth:
				case HtmlCommand.MSHtml.LineBreakLeft:
				case HtmlCommand.MSHtml.LineBreakNormal:
				case HtmlCommand.MSHtml.LineBreakRight:
				case HtmlCommand.MSHtml.List:
				case HtmlCommand.MSHtml.ListBox:
				case HtmlCommand.MSHtml.LiveResize:
				case HtmlCommand.MSHtml.Marquee:
				case HtmlCommand.MSHtml.NonBreak:
				case HtmlCommand.MSHtml.Object:
				case HtmlCommand.MSHtml.OrderList:
				case HtmlCommand.MSHtml.Outdent:
				case HtmlCommand.MSHtml.OverrideCursor:
				case HtmlCommand.MSHtml.Overwrite:
				case HtmlCommand.MSHtml.PageBreak:
				case HtmlCommand.MSHtml.Paragraph:
				case HtmlCommand.MSHtml.PasteFormat:
				case HtmlCommand.MSHtml.PasteInsert:
				case HtmlCommand.MSHtml.PasteSpecial:
				case HtmlCommand.MSHtml.Position2D:
				case HtmlCommand.MSHtml.Preformatted:
				case HtmlCommand.MSHtml.RadioButton:
				case HtmlCommand.MSHtml.Raised:
				case HtmlCommand.MSHtml.RCInsert:
				case HtmlCommand.MSHtml.RemoveFormat:
				case HtmlCommand.MSHtml.RemoveParagraphFormat:
				case HtmlCommand.MSHtml.Rename:
				case HtmlCommand.MSHtml.Replace:
				case HtmlCommand.MSHtml.RightAlignParagraph:
				case HtmlCommand.MSHtml.RowInsert:
				case HtmlCommand.MSHtml.Script:
				case HtmlCommand.MSHtml.Shadowed:
				case HtmlCommand.MSHtml.SpecialCharacter:
				case HtmlCommand.MSHtml.StrikeThrough:
				case HtmlCommand.MSHtml.Subscript:
				case HtmlCommand.MSHtml.Sunken:
				case HtmlCommand.MSHtml.Superscript:
				case HtmlCommand.MSHtml.Table:
				case HtmlCommand.MSHtml.TableInsert:
				case HtmlCommand.MSHtml.TextArea:
				case HtmlCommand.MSHtml.TextBox:
				case HtmlCommand.MSHtml.TriStateBold:
				case HtmlCommand.MSHtml.TriStateItalic:
				case HtmlCommand.MSHtml.TriStateUnderline:
				case HtmlCommand.MSHtml.UIOutdent:
				case HtmlCommand.MSHtml.UnBookmark:
				case HtmlCommand.MSHtml.Underline:
				case HtmlCommand.MSHtml.Ungroup:
				case HtmlCommand.MSHtml.Unlink:
				case HtmlCommand.MSHtml.UnorderList:
					return true;
			}

			return false;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
		private static bool ShouldNotifyHtmlChangedOnExecute(HtmlCommandIdentifier command)
		{
			switch (command)
			{
				case HtmlCommandIdentifier.Cut:
				case HtmlCommandIdentifier.Delete:
				case HtmlCommandIdentifier.Paste:
				case HtmlCommandIdentifier.Redo:
				case HtmlCommandIdentifier.Undo:
				case HtmlCommandIdentifier.Bold:
				case HtmlCommandIdentifier.CreateBookmark:
				case HtmlCommandIdentifier.CreateLink:
				case HtmlCommandIdentifier.FontName:
				case HtmlCommandIdentifier.FontSize:
				case HtmlCommandIdentifier.ForeColor:
				case HtmlCommandIdentifier.FormatBlock:
				case HtmlCommandIdentifier.Indent:
				case HtmlCommandIdentifier.InsertInputButton:
				case HtmlCommandIdentifier.InsertFieldset:
				case HtmlCommandIdentifier.InsertHorizontalRule:
				case HtmlCommandIdentifier.InsertIFrame:
				case HtmlCommandIdentifier.InsertImage:
				case HtmlCommandIdentifier.InsertInputCheckbox:
				case HtmlCommandIdentifier.InsertInputFileUpload:
				case HtmlCommandIdentifier.InsertInputHidden:
				case HtmlCommandIdentifier.InsertInputImage:
				case HtmlCommandIdentifier.InsertInputPassword:
				case HtmlCommandIdentifier.InsertInputRadio:
				case HtmlCommandIdentifier.InsertInputReset:
				case HtmlCommandIdentifier.InsertInputSubmit:
				case HtmlCommandIdentifier.InsertInputText:
				case HtmlCommandIdentifier.InsertMarquee:
				case HtmlCommandIdentifier.InsertOrderedList:
				case HtmlCommandIdentifier.InsertParagraph:
				case HtmlCommandIdentifier.InsertSelectDropdown:
				case HtmlCommandIdentifier.InsertSelectListbox:
				case HtmlCommandIdentifier.InsertTextArea:
				case HtmlCommandIdentifier.InsertUnorderedList:
				case HtmlCommandIdentifier.Italic:
				case HtmlCommandIdentifier.JustifyCenter:
				case HtmlCommandIdentifier.JustifyFull:
				case HtmlCommandIdentifier.JustifyLeft:
				case HtmlCommandIdentifier.JustifyNone:
				case HtmlCommandIdentifier.JustifyRight:
				case HtmlCommandIdentifier.Outdent:
				case HtmlCommandIdentifier.Position2D:
				case HtmlCommandIdentifier.Refresh:
				case HtmlCommandIdentifier.RemoveFormat:
				case HtmlCommandIdentifier.RemoveParaFormat:
				case HtmlCommandIdentifier.StrikeThrough:
				case HtmlCommandIdentifier.Subscript:
				case HtmlCommandIdentifier.Superscript:
				case HtmlCommandIdentifier.UnBookmark:
				case HtmlCommandIdentifier.Underline:
				case HtmlCommandIdentifier.Unlink:
					return true;
			}

			return false;
		}

		private static string GetCommandID(HtmlCommandIdentifier command)
		{
			switch (command)
			{
				case HtmlCommandIdentifier.Position2D:
					return "2D-Position";
				case HtmlCommandIdentifier.ShowAllTags:
					return "IDM_SHOWALLTAGS";
				default:
					string commandID = command.ToString();

					if (commandID == "None")
						return string.Empty;
					else
						return commandID;
			}
		}

		public bool Execute()
		{
			return Execute(false, null);
		}

		public bool Execute(bool showUI)
		{
			return Execute(showUI, null);
		}

		public virtual bool Execute(bool showUI, object value)
		{
			bool success = (document == null)
				? (textRange == null)
						? controlRange.execCommand(commandId, showUI, value)
						: textRange.execCommand(commandId, showUI, value)
				: document.execCommand(commandId, showUI, value);

			if (success && notifyHtmlChangedOnExecute)
				NotifyHtmlChanged();

			return success;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public T GetValue<T>()
		{
			return (T) GetValue(default(T));
		}
		
		public virtual T GetValue<T>(T defaultValue)
		{
			object value = (document == null)
				? (textRange == null)
						? controlRange.queryCommandValue(commandId)
						: textRange.queryCommandValue(commandId)
				: document.queryCommandValue(commandId);

			// according to the docs on MSDN queryCommandValue returns {false} if the operation is not supported
			bool success = !(value is bool) || (bool) value;

			if (!success || value is DBNull)		// browser returns DBNull when the command state is indeterminate
				value = defaultValue;

			return (T) value;
		}

		protected virtual void NotifyHtmlChanged()
		{
			editor.NotifyHtmlChanged(HtmlChangedReason.CommandNotification);
		}
		#endregion
	}
}
